﻿using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Notoric.Commanding;
using System.Windows.Input;

namespace Notoric.View.Music.CWNotation.Controls
{
    /// <summary>
    /// Visualizes a note.
    /// </summary>
    public partial class Note : UserControl
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="Note"/> instance.
        /// </summary>
        public Note()
        {
            InitializeComponent();
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Occurs when the data context (visualized note) has changed.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// The handler unbinds from the old note's <see cref="ViewModel.ViewModelElement.PropertyChanged"/>, 
        /// caches the new visualized note, binds to its <see cref="ViewModel.ViewModelElement.PropertyChanged"/> event
        /// and calls <see cref="SetVisualization"/>.
        /// </remarks>
        /// <param name="sender">Object where the event handler is attached</param>
        /// <param name="e">Event data</param>
        private void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (note != null)
                note.PropertyChanged -= note_PropertyChanged;

            note = (ViewModel.Elements.CWNotation.Note)DataContext;

            if (note != null)
                note.PropertyChanged += new PropertyChangedEventHandler(note_PropertyChanged);

            SetVisualization();
        }

        /// <summary>
        /// Occurs when the contol has been loaded.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// The handler caches the visualized note, binds to its <see cref="ViewModel.ViewModelElement.PropertyChanged"/> event
        /// and calls <see cref="SetVisualization"/>.
        /// </remarks>
        /// <param name="sender">Object where the event handler is attached</param>
        /// <param name="e">Event data</param>
        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            note = (ViewModel.Elements.CWNotation.Note)DataContext;

            if (note != null)
                note.PropertyChanged += new PropertyChangedEventHandler(note_PropertyChanged);

            SetVisualization();
        }

        /// <summary>
        /// Occurs when the value of some of the note's properties has changed.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// If the modified property is <see cref="Notoric.ViewModel.Elements.CWNotation.DurationalSymbol.BasicDuration"/>,
        /// the handler calls the <see cref="SetVisualization"/> method.
        /// </remarks>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void note_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "BasicDuration")
                SetVisualization();
            else if (e.PropertyName == "DotsPosition")
            {
                var staffSpaces = note.FindOwner<ViewModel.Elements.CWNotation.Staff>().Lines.Count - 1;
                var spaceSize = (staffSpaces > 0 ? note.EffectiveTypeSize / staffSpaces : note.FindOwner<ViewModel.Elements.CWNotation.Staff>().SpaceSize);
                PART_DotsPosition.Margin = new Thickness(note.DotsPosition - 5.5 * spaceSize);
            }
        }

        /// <summary>
        /// Handles the Click event of the menu_StemUp control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void menu_StemUp_Click(object sender, RoutedEventArgs e)
        {
            if (note != null && note.FindOwner<ViewModel.Elements.CWNotation.Chord>().StemDirection != ViewModel.Elements.CWNotation.StemOrientation.Up)
            {
                var cmd = note.FindOwner<ViewModel.Elements.CWNotation.Chord>().CreateSetStemDirectionCommand(ViewModel.Elements.CWNotation.StemOrientation.Up);
                var lay = new ViewModel.MusicSectionInteriorLayoutCommand(cmd.UndoStack, cmd.RedoStack)
                {
                    Interior = note.FindOwner<ViewModel.Elements.CWNotation.MusicSectionInterior>()
                };
                var macro = new UndoableMacroCommand(cmd.UndoStack, cmd.RedoStack);
                macro.Commands.Add(cmd);
                macro.Commands.Add(lay);
                macro.Execute(null);
            }
        }

        /// <summary>
        /// Handles the Click event of the menu_StemDown control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void menu_StemDown_Click(object sender, RoutedEventArgs e)
        {
            if (note != null && note.FindOwner<ViewModel.Elements.CWNotation.Chord>().StemDirection != ViewModel.Elements.CWNotation.StemOrientation.Down)
            {
                var cmd = note.FindOwner<ViewModel.Elements.CWNotation.Chord>().CreateSetStemDirectionCommand(ViewModel.Elements.CWNotation.StemOrientation.Down);
                var lay = new ViewModel.MusicSectionInteriorLayoutCommand(cmd.UndoStack, cmd.RedoStack)
                {
                    Interior = note.FindOwner<ViewModel.Elements.CWNotation.MusicSectionInterior>()
                };
                var macro = new UndoableMacroCommand(cmd.UndoStack, cmd.RedoStack);
                macro.Commands.Add(cmd);
                macro.Commands.Add(lay);
                macro.Execute(null);
            }
        }

        /// <summary>
        /// Handles the Click event of the menu_Articulation control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void menu_Articulation_Click(object sender, RoutedEventArgs e)
        {
            var menuItem = (MenuItem)sender;

            if (note != null)
            {
                ViewModel.Elements.CWNotation.ArticulationType articulation;

                switch (menuItem.Name)
                {
                    case "menu_ArticulationLegato":
                        articulation = ViewModel.Elements.CWNotation.ArticulationType.Legato;
                        break;
                    case "menu_ArticulationStaccato":
                        articulation = ViewModel.Elements.CWNotation.ArticulationType.Staccato;
                        break;
                    case "menu_ArticulationStaccatissimo":
                        articulation = ViewModel.Elements.CWNotation.ArticulationType.Staccatissimo;
                        break;
                    case "menu_ArticulationPortamento":
                        articulation = ViewModel.Elements.CWNotation.ArticulationType.Portamento;
                        break;
                    case "menu_ArticulationTenuto":
                        articulation = ViewModel.Elements.CWNotation.ArticulationType.Tenuto;
                        break;
                    default:
                        return;
                }

                var cmd = note.FindOwner<ViewModel.Elements.CWNotation.Chord>().CreateSetArticulationCommand(articulation);
                cmd.Execute(null);
            }
        }

        /// <summary>
        /// Handles the Click event of the menu_Accidental control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void menu_Accidental_Click(object sender, RoutedEventArgs e)
        {
            var menuItem = (MenuItem)sender;

            if (note != null)
            {
                ViewModel.Elements.CWNotation.AccidentalType accidental;

                if (menuItem == menu_AccidentalDoubleFlat)
                    accidental = ViewModel.Elements.CWNotation.AccidentalType.DoubleFlat;
                else if (menuItem == menu_AccidentalDoubleNeutral)
                    accidental = ViewModel.Elements.CWNotation.AccidentalType.DoubleNeutral;
                else if (menuItem == menu_AccidentalDoubleSharp)
                    accidental = ViewModel.Elements.CWNotation.AccidentalType.DoubleSharp;
                else if (menuItem == menu_AccidentalFlat)
                    accidental = ViewModel.Elements.CWNotation.AccidentalType.Flat;
                else if (menuItem == menu_AccidentalNeutral)
                    accidental = ViewModel.Elements.CWNotation.AccidentalType.Neutral;
                else if (menuItem == menu_AccidentalSharp)
                    accidental = ViewModel.Elements.CWNotation.AccidentalType.Sharp;
                else
                    return;

                var cmd = note.Accidental.CreateSetTypeCommand(accidental);
                cmd.Execute(null);
            }
        }

        /// <summary>
        /// Handles the PreviewMouseLeftButtonDown event of the PART_HeadSymbol control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
        private void PART_HeadSymbol_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (ApplicationBridge.Current.State.MouseTarget == typeof(MusicSectionInterior))
            {
                if (ApplicationBridge.Current.State.Command is ViewModel.AddDynamicMarkCommand)
                {
                    ApplicationBridge.Current.State.Command.SetParameterValue<ViewModel.Elements.CWNotation.DurationalSymbol>("Symbol", note.FindOwner<ViewModel.Elements.CWNotation.Chord>());
                    ApplicationBridge.Current.State.Command.Execute(null);
                    ApplicationBridge.Current.State.Command = null;
                    ApplicationBridge.Current.State.MouseTarget = null;
                    ApplicationBridge.Current.State.StatusBarMessage = "Ready";
                }
                else if (ApplicationBridge.Current.State.Command is ViewModel.AddDynamicsProgressMarkCommand)
                {
                    if (!ApplicationBridge.Current.State.Command.CanComputeParameterValue("Start", null))
                        ApplicationBridge.Current.State.Command.SetParameterValue<ViewModel.Elements.CWNotation.DurationalSymbol>("Start", note.FindOwner<ViewModel.Elements.CWNotation.Chord>());
                    else
                    {
                        ApplicationBridge.Current.State.Command.SetParameterValue<ViewModel.Elements.CWNotation.DurationalSymbol>("End", note.FindOwner<ViewModel.Elements.CWNotation.Chord>());
                        ApplicationBridge.Current.State.Command.Execute(null);
                        ApplicationBridge.Current.State.Command = null;
                        ApplicationBridge.Current.State.MouseTarget = null;
                        ApplicationBridge.Current.State.StatusBarMessage = "Ready";
                    }
                }
            }
            else
            {
                if (Keyboard.IsKeyUp(Key.LeftCtrl) && Keyboard.IsKeyUp(Key.RightCtrl))
                    note.FindOwner<ViewModel.Elements.CWNotation.MusicSectionInterior>().ClearSelection();
                note.FindOwner<ViewModel.Elements.CWNotation.MusicSectionInterior>().AddToSelection(note.FindOwner<ViewModel.Elements.CWNotation.Chord>());
            }
        }
        
        #endregion

        #region Visual Overrides

        /// <inheritdoc/>
        protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
        {
            var rect = new Rect(new Point(note.HeadPosition, note.VerticalPosition - spaceSize / 2), new Size(spaceSize, spaceSize));

            if (rect.Contains(hitTestParameters.HitPoint))
                return new PointHitTestResult(PART_HeadSymbol, hitTestParameters.HitPoint);

            return null;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Selects the symbol and margin for the <see cref="PART_HeadSymbol"/> control
        /// to visualize the note's type.
        /// </summary>
        private void SetVisualization()
        {
            if (note == null)
            {
                PART_HeadSymbol.Text = "";
                return;
            }

            var staffSpaces = note.FindOwner<ViewModel.Elements.CWNotation.Staff>().Lines.Count - 1;
            spaceSize = (staffSpaces > 0 ? note.EffectiveTypeSize / staffSpaces : note.FindOwner<ViewModel.Elements.CWNotation.Staff>().SpaceSize);

            switch (note.FindOwner<ViewModel.Elements.CWNotation.Chord>().BasicDuration)
            {
                case 256:
                    PART_HeadSymbol.Text = "W";
                    break;
                case 128:
                    PART_HeadSymbol.Text = "w";
                    break;
                case 64:
                    PART_HeadSymbol.Text = "ú";
                    break;
                default:
                    PART_HeadSymbol.Text = "Ï";
                    break;
            }

            PART_HeadSymbol.Margin = new Thickness(0, -5.5 * spaceSize, 0, 0);
            PART_DotsPosition.Margin = new Thickness(note.DotsPosition - 5.5 * spaceSize);
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the visualized note.
        /// </summary>
        private ViewModel.Elements.CWNotation.Note note;
        /// <summary>
        /// Caches the space size.
        /// </summary>
        private double spaceSize;

        #endregion       
    }
}
